package com.zyf.project.binaryproto.converter.impl.dynamic;

import com.zyf.project.binaryproto.annotation.DataContract;
import com.zyf.project.binaryproto.contract.DataContractEncoder;
import com.zyf.project.binaryproto.contract.DataContractEncoderLookup;
import com.zyf.project.binaryproto.converter.impl.AbstractDynamicValueConverter;
import com.zyf.project.binaryproto.encoder.impl.HeaderEncoder;
import com.zyf.project.binaryproto.exception.DataContractException;
import org.jetbrains.annotations.NotNull;
import utils.binary.impl.BytesSlice;
import utils.binary.io.BytesOutputBuffer;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Zhou Yifan
 */
public class DataContractGenericRefConverter extends AbstractDynamicValueConverter {

    /**
     * 互斥锁
     */
    private final Object mutex = new Object();

    /**
     * 二进制编码检索器
     */
    private DataContractEncoderLookup encoderLookup;

    /**
     * 二进制编码器缓存，key 为添加了 {@link DataContract} 注解的类，value 为 {@link DataContractEncoder}
     */
    private Map<Class<?>, DataContractEncoder> encoderCache;

    public DataContractGenericRefConverter(Class<?> baseType, DataContractEncoderLookup encoderLookup) {
        super(baseType);
        this.encoderLookup = encoderLookup;
        // 使用 ConcurrentHashMap 保证线程安全
        this.encoderCache = new ConcurrentHashMap<>();
    }

    /**
     * 根据指定类型检索二进制编码器
     *
     * @param dataObjectType 指定类型
     * @return {@link DataContractEncoder}
     */
    private DataContractEncoder lookupEncoder(Class<?> dataObjectType) {
        // 获取 dataObjectType 数据契约的二进制编码器
        DataContractEncoder encoder = encoderCache.get(dataObjectType);
        if (encoder != null) {
            return encoder;
        }
        // 如果编码器存在，加上互斥锁（同一时刻只能有一个线程持有），此时仅有该线程工作
        synchronized (mutex) {
            encoder = encoderCache.get(dataObjectType);
            if (encoder != null) {
                return encoder;
            }
            // 获取接口类型（或者实现的接口类型，可能有多个）
            Class<?>[] interfaces = dataObjectType.getInterfaces();
            Class<?> contractType = null;
            DataContract anno = null;
            // 遍历所有的接口
            for (Class<?> itf : interfaces) {
                // 获取数据契约注解
                anno = itf.getAnnotation(DataContract.class);
                if (anno != null) {
                    if (contractType == null) {
                        contractType = itf;
                    } else {
                        // DataContract 实现一个即可
                        throw new DataContractException(String.format(
                                "Data object implements more than one DataContract interface! --[DataObject=%s]",
                                dataObjectType.toString()));
                    }
                }
            }
            // 如果没有添加 DataContract 注解，那么抛出异常（一般也不允许这样做）
            if (contractType == null) {
                throw new DataContractException(String.format(
                        "Data object doesn't implement any DataContract interface! --[DataObject=%s]",
                        dataObjectType.toString()));
            }
            // 根据数据契约的类型，寻找对应的二进制编码器
            encoder = encoderLookup.lookup(contractType);
            // 如果找不到，那么就说明这个编码器还未注册
            if (encoder == null) {
                throw new DataContractException(String.format(
                        "DataContract of the specified data object hasn't been registered! --[DataContract=%s][DataObject=%s]",
                        contractType.toString(), dataObjectType.toString()));
            }
            // 找到之后就把这个编码器放到编码器缓存中
            encoderCache.put(dataObjectType, encoder);
        }

        return encoder;
    }

    @Override
    public int encode(@NotNull Object value, BytesOutputBuffer buffer) {
        // 首先要找到 value 对应类型的二进制编码器
        DataContractEncoder contractEncoder = lookupEncoder(value.getClass());

        BytesOutputBuffer contractBuffer = new BytesOutputBuffer();
        int size = contractEncoder.encode(value, contractBuffer);

        size += writeSize(size, buffer);

        buffer.write(contractBuffer);
        return size;
    }

    @Override
    public Object decode(BytesSlice dataSlice) {
        // 首先要找到编码器编码和版本，然后根据编码和版本来找到编码器，从而进行解码
        int code = HeaderEncoder.resolveCode(dataSlice);
        long version = HeaderEncoder.resolveVersion(dataSlice);
        DataContractEncoder contractEncoder = encoderLookup.lookup(code, version);
        if (contractEncoder == null) {
            throw new DataContractException(
                    String.format("No data contract was registered with code[%s] and version[%s]!", code, version));
        }
        return contractEncoder.decode(dataSlice.getInputStream());
    }

}
